/*
 * Copyright 2011 Angel Sanadinov
 *
 * This file is part of VBox WMI.
 *
 * VBox WMI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * VBox WMI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with VBox WMI.  If not, see <http://www.gnu.org/licenses/>.
 */

package ApplicationManagement;

import Beans.Requests.RequestBean;
import Beans.Responses.ResponseBean;
import Managers.LogsManager;
import Utilities.Constants;
import Utilities.Constants.ReportingLoggingLevel;
import Utilities.ParameterNames;
import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

/**
 * This class represents an action reporting filter. <br><br>
 *
 * Depending on the application's configuration, this filter will log all requests
 * and/or their responses in the database.
 *
 * @author Angel Sanadinov
 */
public class ActionReporting implements Filter
{
    private ServletContext context; //filter context
    private LogsManager logsManager;//logs manager
    private ReportingLoggingLevel loggingLevel;//logging level

    //regular expression for matching the attribute name of the request object
    private static Pattern requestMatchingPattern =
            Pattern.compile(ParameterNames.REQUEST_LOGIN + "|" + ParameterNames.REQUEST_LOGOUT + "|"
                          + ParameterNames.REQUEST_MANAGE_USER + "|" + ParameterNames.REQUEST_MANAGE_SERVER + "|"
                          + ParameterNames.REQUEST_MANAGE_MEDIUM + "|" + ParameterNames.REQUEST_MANAGE_MACHINE + "|"
                          + ParameterNames.REQUEST_MANAGE_NETWORK + "|" + ParameterNames.REQUEST_DATA_RETRIEVAL);

    //regular expression for matching the attribute name of the response object
    private static Pattern responseMatchingPattern =
            Pattern.compile(ParameterNames.RESPONSE_LOGIN + "|" + ParameterNames.RESPONSE_MANAGE_USER + "|"
                          + ParameterNames.RESPONSE_MANAGE_SERVER + "|" + ParameterNames.RESPONSE_MANAGE_MEDIUM + "|"
                          + ParameterNames.RESPONSE_MANAGE_MACHINE + "|" + ParameterNames.RESPONSE_MANAGE_NETWORK + "|"
                          + ParameterNames.RESPONSE_DATA_RETRIEVAL);

    /**
     * Initializes the filter.
     *
     * @param filterConfig filter configuration
     *
     * @throws ServletException if an exception occurs that interrupts the filter's
     *                          normal operation
     *
     * @see Filter#init()
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
        context = filterConfig.getServletContext();
        loggingLevel = ReportingLoggingLevel.NONE;
    }

    /**
     * Ends the filter's life-cycle and cleans up any object references.
     *
     * @see Filter#destroy()
     */
    @Override
    public void destroy()
    {
        context = null;
        logsManager = null;
        loggingLevel = null;
    }

    /**
     * Logs all request and/or response data, based on the application's configuration.
     *
     * @param request The servlet request being processed
     * @param response The servlet response being processed
     * @param chain The filter chain being processed
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet error occurs
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
        //retrieves the logs manager, if it is not available
        if(logsManager == null)
            logsManager = (LogsManager)context.getAttribute(Constants.CONTEXT_LOGS_MANAGER);
        else
            ;

        //attempts to retrieve the compression algorithm type specified in the configuration file
        try
        {
            loggingLevel = ReportingLoggingLevel.valueOf(
                    context.getInitParameter(ParameterNames.REPORTING_LOGGING_LEVEL).toUpperCase());
        }
        catch(Exception e)
        {
            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_WARNING,
                                       "Reporting Filter: Failed to parse initialization parameter. "
                                       + "Default value (" + loggingLevel + ") for loggingLevel will be used.");
        }

         //checks if an HTTP request was made and if the logs manager was retrieved
        if(request instanceof HttpServletRequest && logsManager != null)
        {
            HttpServletRequest httpRequest = (HttpServletRequest)request;

            //determines the logging level
            switch(loggingLevel)
            {
                //logs only requests
                case REQUEST:
                {
                    String requestAttributeName = getRequiredAttributeName(httpRequest, true);
                    if(requestAttributeName != null)
                    {
                        RequestBean clientRequest = (RequestBean)httpRequest.getAttribute(requestAttributeName);
                        logsManager.log(System.currentTimeMillis(), Constants.LOG_SEVERITY_INFORMATION,
                                        clientRequest.toString(), clientRequest.getRequestorId(),
                                        clientRequest.getServerId());
                    }
                    else
                        ;

                    chain.doFilter(request, response);
                } break;

                //logs only responses
                case RESPONSE:
                {
                    chain.doFilter(request, response);

                    String responseAttributeName = getRequiredAttributeName(httpRequest, false);
                    if(responseAttributeName != null)
                    {
                        ResponseBean clientResponse = (ResponseBean)httpRequest.getAttribute(responseAttributeName);
                        logsManager.log(System.currentTimeMillis(), Constants.LOG_SEVERITY_INFORMATION,
                                        clientResponse.toString(), 0);
                    }
                    else
                        ;
                } break;

                //logs both requests and responses
                case REQUEST_RESPONSE:
                {
                    String requestAttributeName = getRequiredAttributeName(httpRequest, true);
                    RequestBean clientRequest = null;
                    if(requestAttributeName != null)
                    {
                        clientRequest = (RequestBean)httpRequest.getAttribute(requestAttributeName);
                        logsManager.log(System.currentTimeMillis(), Constants.LOG_SEVERITY_INFORMATION,
                                        clientRequest.toString(), clientRequest.getRequestorId(),
                                        clientRequest.getServerId());
                    }
                    else
                        ;

                    chain.doFilter(request, response);

                    String responseAttributeName = getRequiredAttributeName(httpRequest, false);
                    if(responseAttributeName != null)
                    {
                        ResponseBean clientResponse = (ResponseBean)httpRequest.getAttribute(responseAttributeName);
                        logsManager.log(System.currentTimeMillis(), Constants.LOG_SEVERITY_INFORMATION,
                                        clientResponse.toString(), clientRequest.getRequestorId(),
                                        clientRequest.getServerId());
                    }
                    else
                        ;
                } break;

                default: chain.doFilter(request, response); break;
            }
        }
        else
            throw new ServletException("Failed to retrieve HTTP request object and/or logging manager");
    }

    /**
     * Retrieves the attribute name for the request/response object currently in
     * the HTTP request. <br><br>
     *
     * <b>Note:</b> <i>This method will retrieve only the first attribute that
     *              matches the regular expression. This could cause issues when
     *              multiple requests are set (e.g.: creating machines with one
     *              or more new disks).</i>
     *
     * @param httpRequest the current HTTP request
     * @param isRequest set to <code>true</code> if a client request is to be
     *        retrieved or set to <code>false</code> if a client response is to be
     *        retrieved
     *
     * @return the required attribute name or <code>null</code> if nothing was found
     */
    private String getRequiredAttributeName(HttpServletRequest httpRequest, boolean isRequest)
    {
        //retrieves the appropriate matching pattern
        Pattern requestedPattern = null;
        if(isRequest)
            requestedPattern = requestMatchingPattern;
        else
            requestedPattern = responseMatchingPattern;
        
        String requiredAttributeName = null;
        Matcher currentMatcher = null;

        //retrieves the attribute names list
        Enumeration<String> attributeNamesList = httpRequest.getAttributeNames();
        while(attributeNamesList.hasMoreElements())
        {
            //attempts to match each attribute name
            currentMatcher = requestedPattern.matcher(attributeNamesList.nextElement());
            if(currentMatcher.matches())
            {
                requiredAttributeName = currentMatcher.group(0);
                break;
            }
            else
                ;
        }

        return requiredAttributeName;
    }
}
